#include "WRenderer.h"

#include <iostream>

#include "RTSCamera.h"
#include "Settings.h"
#include "GL\glew.h"
#include "CheckError.h"
#include "SceneGraph.h"
#include "BindingPt.h" 
#include "Shader.h"

#include "InputListener.h"

#include "WCube.h"
#include "WLine.h"

#include <IL/il.h>
#include "glm\gtc\matrix_transform.hpp"

const char* WRenderer::sharedUniformVariables[] = 
{
	"projection", "view", "viewProjMat"
};

WRenderer::RenderPass WRenderer::Pass = WRenderer::NORMAL;
std::shared_ptr<WRenderer> WRenderer::renderer = nullptr;

WRenderer::WRenderer(bool& running) : WContext(running)
{
	WRenderer::renderer = std::shared_ptr<WRenderer>(this);

	settings = new sf::ContextSettings();
	settings->depthBits = 24;
	settings->stencilBits = 8;
	settings->antialiasingLevel = 2;
	settings->majorVersion = 4;
	settings->minorVersion = 3;

	CreateContext();
	
	Shader::InitShaders();
	Shader::LoadShaders();

	projectionMatrix = glm::perspective(60.0f, 16.0f / 9.0f, 0.1f, 10000.0f);
	mainCamera = std::shared_ptr<RTSCamera>(new RTSCamera(true));
	mainCamera->Translate(glm::vec3(0.0f, 0.0f, 5.0f));

	light = std::shared_ptr<DirectionalLight>(new DirectionalLight());
	WObject::RegisterPreUpdater(light);

	shadowMap = std::shared_ptr<ShadowMap>(new ShadowMap());
	shadowMap->Init(WINDOW_WIDTH, WINDOW_HEIGHT, light);
	WObject::RegisterPreUpdater(shadowMap);

	//init devil
	// Before calling ilInit() version should be checked. 
	if (ilGetInteger(IL_VERSION_NUM) < IL_VERSION)
	{
		/// wrong DevIL version ///
		std::string err_msg = "Wrong DevIL version. Old devil.dll in system32/SysWow64?";
		char* cErr_msg = (char *) err_msg.c_str();
		return;
	}

	ilInit(); // Initialization of DevIL

	initiated = false;
}

WRenderer::~WRenderer()
{
	delete window;
	delete settings;
}

void WRenderer::CreateContext()
{
	//create window
	window = new sf::Window (sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "OpenGL", sf::Style::Default, *settings);

	window->setVerticalSyncEnabled(true);
	window->setActive(true);
	window->setVisible(true);

	glewExperimental = true;
	GLenum err = glewInit();
	while(err!=GL_NO_ERROR) {
		std::string error;

		switch(err) {
		case GL_INVALID_OPERATION:      error="INVALID_OPERATION";      break;
		case GL_INVALID_ENUM:           error="INVALID_ENUM";           break;
		case GL_INVALID_VALUE:          error="INVALID_VALUE";          break;
		case GL_OUT_OF_MEMORY:          error="OUT_OF_MEMORY";          break;
		case GL_INVALID_FRAMEBUFFER_OPERATION:  error="INVALID_FRAMEBUFFER_OPERATION";  break;
		}

		std::cerr << "GL_" << error.c_str()<<std::endl;
		err=glGetError();
	}

	created = true;
}

void WRenderer::Init()
{
	//generate the one-per-scene uniform buffer object
	glGenBuffers(1, &sharedUniformBuffer);
	glBindBuffer(GL_UNIFORM_BUFFER, sharedUniformBuffer);

	glBufferData(GL_UNIFORM_BUFFER, sizeof(uParams), &uParams, GL_DYNAMIC_DRAW);
	glBindBufferBase(GL_UNIFORM_BUFFER, VIEW_PROJ_UBO, sharedUniformBuffer);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);

	//generate per object buffers
	glGenBuffers(1, &shaderParamsID);
	glBindBuffer(GL_UNIFORM_BUFFER, shaderParamsID);
	int bufferSize = sizeof(shaderParams);
	for(int i=0; i<200; i++)
	{
		shaderParams.model[i] = glm::mat4(1.0f);
	}

	glBufferData(GL_UNIFORM_BUFFER, bufferSize, &shaderParams, GL_DYNAMIC_DRAW);
	glBindBufferBase(GL_UNIFORM_BUFFER, SHADER_PARAM, shaderParamsID);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	
}


void WRenderer::PollInput() 
{
	sf::Event event;

	while(window->pollEvent(event))
	{

		if (event.type == sf::Event::Closed)
		{
			// end the program
			running = false;
		}
		else if(event.type == sf::Event::Resized)
		{
			glViewport(0, 0, event.size.width, event.size.height);
		}

		PollMouseInput(event);

	}
}

void WRenderer::PollMouseInput(sf::Event& event)
{
	std::shared_ptr<InputListener> listener = InputListener::inputStack.top();
	if (event.type == sf::Event::MouseButtonPressed)
	{
		std::shared_ptr<MouseInput> newInput = std::shared_ptr<MouseInput>(new MouseInput());
		newInput->key = event.mouseButton.button;
		newInput->type = MousePress;
		newInput->x = event.mouseButton.x;
		newInput->y = event.mouseButton.y;
		listener->EnqueueInput(newInput);
	}
	else if(event.type == sf::Event::MouseButtonReleased)
	{
		std::shared_ptr<MouseInput> newInput = std::shared_ptr<MouseInput>(new MouseInput());
		newInput->key = event.mouseButton.button;
		newInput->type = MouseRelease;
		newInput->x = event.mouseButton.x;
		newInput->y = event.mouseButton.y;
		listener->EnqueueInput(newInput);
	}
	else if(event.type == sf::Event::MouseWheelMoved)
	{
		std::shared_ptr<MouseInput> newInput = std::shared_ptr<MouseInput>(new MouseInput());
		newInput->key = event.mouseButton.button;
		newInput->type = MouseWheel;
		newInput->x = event.mouseWheel.x;
		newInput->y = event.mouseWheel.y;
		newInput->delta = (float) event.mouseWheel.delta;
		listener->EnqueueInput(newInput);
	}	
	else if(event.type == sf::Event::MouseMoved)
	{
		std::shared_ptr<MouseInput> newInput = std::shared_ptr<MouseInput>(new MouseInput());
		newInput->key = event.mouseButton.button;
		newInput->type = MouseMove;
		newInput->x = event.mouseMove.x;
		newInput->y = event.mouseMove.y;
		listener->EnqueueInput(newInput);
	}
}

void WRenderer::RenderLoop()
{
	while(true)
	{
		if(running)
		{
			if(!initiated)
			{
				std::shared_ptr<SceneGraph> defaultSceneGraph = std::shared_ptr<SceneGraph>(SceneGraph::GetDefaultSceneGraph());
				std::shared_ptr<SceneNode> root = std::shared_ptr<SceneNode>(defaultSceneGraph->GetRoot());

				WObject::RegisterUpdater(root);

				initiated = true;
			}

			if(loadingQueue.size() > 0)
			{
				std::shared_ptr<WLoadItem> loadItem = loadingQueue.front();
				std::shared_ptr<SceneNode> newNode = nullptr;
				Import3DFromFile(loadItem->GetPath().c_str(), newNode);
				loadingQueue.pop();
			}

			if(initiated)
			{
				PollInput();
				InputListener::inputStack.top()->ProcessInputQueue();

				PreUpdate();
				Update();

				glEnable(GL_DEPTH_TEST);
				glDepthFunc(GL_LESS);
				
				glEnable(GL_CULL_FACE);
				glCullFace(GL_BACK);

				//start the current frame -- let's start our rendering				
				glClearColor(0, 0, 0, 0);
				glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

				PreRender();
				Render();

				// end the current frame -- this is a rendering function (it requires the context to be active)
				window->display();
				glDisable(GL_DEPTH_TEST);
			}
		}
		else 
		{
			break;
		}
	}
}

void WRenderer::PreUpdate()
{
	WObject::PreUpdateObjects();

}

void WRenderer::Update()
{
	WObject::UpdateObjects(renderer);

	glBindBuffer(GL_UNIFORM_BUFFER, shaderParamsID);
	int bufferSize = sizeof(shaderParams);
	glBufferData(GL_UNIFORM_BUFFER, bufferSize, &shaderParams, GL_DYNAMIC_DRAW);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

void WRenderer::PreRender()
{
	Pass = WRenderer::SHADOWPASS;

	UpdateUBOForShadowPass();
	RenderShadow();
}

void WRenderer::Render()
{
	Pass = WRenderer::NORMAL;

	UpdateUBOForRender();
	
	std::shared_ptr<SceneGraph> defaultSceneGraph = std::shared_ptr<SceneGraph>(SceneGraph::GetDefaultSceneGraph());
	std::shared_ptr<SceneNode> root = std::shared_ptr<SceneNode>(defaultSceneGraph->GetRoot());

	//skyBoxNode->Render(Pass);
	root->Render(Pass);
}

void WRenderer::UpdateUBOForRender()
{
	glBindBuffer(GL_UNIFORM_BUFFER, sharedUniformBuffer);

	//set viewproj matrix content
	uParams.projection = projectionMatrix;
	uParams.view = mainCamera->GetViewMatrix();
	uParams.viewProjMat = projectionMatrix * uParams.view;
	uParams.biasMat = shadowMap->GetBiasMatrix();
	uParams.shadowProjection = shadowMap->GetProjectionMatrix();
	uParams.worldToLightMat = shadowMap->GetViewMatrix();
	
	glm::vec3 camPosition = mainCamera->GetPosition();
	uParams.camPosition = glm::vec4(camPosition.x, camPosition.y, camPosition.z, 0.0f);

	glm::vec3 lightDir = light->GetForward();
	uParams.lightDir = glm::vec4(lightDir.x, lightDir.y, lightDir.z, 0.0f);
	
	//map gpu ubo content
	void* ptr = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY);
	memcpy(ptr, &uParams, sizeof(uParams));
	glUnmapBuffer(GL_UNIFORM_BUFFER);

	glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

void WRenderer::UpdateUBOForShadowPass()
{
	glBindBuffer(GL_UNIFORM_BUFFER, sharedUniformBuffer);

	//set viewproj matrix content
	uParams.projection = projectionMatrix;
	uParams.view = mainCamera->GetViewMatrix();
	uParams.viewProjMat = shadowMap->GetVPMatrix();
	uParams.biasMat = shadowMap->GetBiasMatrix();
	uParams.shadowProjection = shadowMap->GetProjectionMatrix();
	uParams.worldToLightMat = shadowMap->GetViewMatrix();
	
	glm::vec3 camPosition = mainCamera->GetPosition();
	uParams.camPosition = glm::vec4(camPosition.x, camPosition.y, camPosition.z, 0.0f);

	glm::vec3 lightDir = light->GetForward();
	uParams.lightDir = glm::vec4(lightDir.x, lightDir.y, lightDir.z, 0.0f);
	
	//map gpu ubo content
	void* ptr = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY);
	memcpy(ptr, &uParams, sizeof(uParams));
	glUnmapBuffer(GL_UNIFORM_BUFFER);

	glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

void WRenderer::RenderShadow()
{
	shadowMap->Bind();
	GLenum e = glGetError();

	glClearColor(0, 0, 0, 0);
	glClear(GL_DEPTH_BUFFER_BIT);

	std::shared_ptr<SceneGraph> defaultSceneGraph = std::shared_ptr<SceneGraph>(SceneGraph::GetDefaultSceneGraph());
	std::shared_ptr<SceneNode> root = std::shared_ptr<SceneNode>(defaultSceneGraph->GetRoot());

	root->Render(Pass);
	
	shadowMap->Unbind();
}